package q2_cas_test;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;


@Getter
@ToString
@AllArgsConstructor
class User {
    String name;
    int age;
}

/*
1、什么是CAS？
    CAS全称为Compare-And-Swap，它是一条CPU并发原语。
    它的功能是判断某个位置的值是否为预期值，如果是则更改为新的值，这个过程是原子的。
    CAS并发原语体现在JAVA语言中就是sum.misc.Unsafe类中的各个方法。调用UnSafe类中的CAS方法，JVM会帮我们实现出CAS汇编指令。
    这是一种完全依赖于硬件的功能，通过它实现了原子操作。
    再次强调，由于CAS是一种系统原语，原语属于操作系统用户范畴，是由若干条指令组成的，用于完成某个功能的一个过程，并且原语的执行必须是连续的，在执行过程中不允许被中断，也就是说CAS是一条CPU的原子指令，不会造成所谓的数据不一致问题。
2、CAS原理
    1) 自旋（UnSafe类 + CAS思想，代码实现原理）
    2) UnSafe类: 是CAS的核心类，由于JAVA方法无法直接访问底层系统，需要通过本地（native）方法来访问。
        UnSafe相当于一个后门，基于该类可以直接操作特定内存的数据。UnSafe类存在于sun.misc包中，
        其内部方法操作可以像C的指针一样直接操作内存，因为JAVA中CAS操作的执行依赖于UnSafe类的方法。
        注意：UnSafe类中的所有方法都是native修饰的，也就是说UnSafe类中的方法都直接调用操作系统底层资源执行相应任务。
3、CAS缺点
    1）循环时间长开销很大
        我们可以看到getAndAddInt方法执行时，有个do...while循环，
        如果CAS失败，会一直进行尝试。如果CAS长时间一直不成功，可能会给CPU带来很大的开销。
    2）只能保证一个共享变量的原子操作
        当对一个共享变量执行操作时，我们可以使用循环CAS的方式来保证原子操作，
        但是，对多个共享变量操作时，循环CAS就无法保证操作的原子性，这个时候就可以用锁来保证原子性。
    3）引出来ABA问题？？？
        a) ABA问题怎么产生的？
            CAS会导致“ABA问题”
            CAS算法实现一个重要前提需要取出内存中某时刻的数据并在当下时刻比较并替换，那么在这个时间差类会导致数据的变化。
            比如说一个线程one从内存位置V中取出A，这时候另一个线程two也从内存中取出A，并且two进行了一些操作将值变成了B，
            然后线程two又将V位置的数据变成A，这时候线程one进行CAS操作发现内存中仍然是A，然后线程one操作成功。
            尽管线程one的CAS操作成功，但是不代表这个过程就是没有问题的。
        b) 原子引用
            AtomicReference类
        c) 时间戳原子引用
            AtomicStampedReference类
 */
public class CASDemo {

    public static void main(String[] args) {
        casDemo();

        atomicReferenceDemo();

        abaDemo();
    }

    public static void casDemo() {
        AtomicInteger atomicInteger = new AtomicInteger(5);

        System.out.println(atomicInteger.compareAndSet(5, 2019) + "\t current data : " + atomicInteger.get());
        System.out.println(atomicInteger.compareAndSet(5, 1024) + "\t current data : " + atomicInteger.get());

        /*
        public final int getAndIncrement() {
            //this：表示当前对象；valueOffset表示内存偏移量
            return unsafe.getAndAddInt(this, valueOffset, 1);
        }

        var1 AtomicInteger对象本身
        var2 该对象值的引用地址
        var4 需要变动的数量
        var5 是通过var1 var2找出的主内存中真实的值
        用该对象当前的值与var5比较：
            如果相同，更新var5 + var4并且返回true；如果不同，继续取值然后比较，直到更新完成。

        假设线程A和线程B两个线程同时执行getAndAddInt操作（分别跑在不同CPU上）：
        1、AtomicInteger里面的value原始值为3，即主内存中AtomicInteger的value为3，
           根据JMM模型，线程A和线程B各自持有一份值为3的value的副本分别在各自的工作内存。
        2、线程A通过getIntVolatile(var1, var2)拿到value值为3，这时线程A挂起。
        3、线程B也通过getIntVolatile(var1, var2)方法获取到value值3，
           此时刚好线程B没有被挂起并执行compareAndSwapInt方法比较内存值也为3，成功修改内存值为4，
           线程B打完收工，一切OK。
        4、这时线程A恢复，执行compareAndSwapInt方法比较，发现自己手里的值数字3和主内存的值数字4不一致，
           说明该值已经被其它线程抢先一步修改过了，那A线程本次修改失败，只能重新读取重新来一遍了。
        5、线程A重新获取value值，因为变量value被volatile修饰，所以其它线程对它的修改，线程A总是能够看到，
           线程A继续执行compareAndSwapInt进行比较替换，直到成功。

        public final int getAndAddInt(Object var1, long var2, int var4) {
            int var5;
            do {
                var5 = this.getIntVolatile(var1, var2);
            } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

            return var5;
        }
         */
        atomicInteger.getAndIncrement();
    }

    public static void atomicReferenceDemo() {
        //原子引用示例
        User u1 = new User("u1", 22);
        User u2 = new User("u2", 25);

        AtomicReference<User> atomicReference = new AtomicReference<>();
        atomicReference.set(u1);

        System.out.println(atomicReference.compareAndSet(u1, u2) + "\t" + atomicReference.get().toString());
        System.out.println(atomicReference.compareAndSet(u1, u2) + "\t" + atomicReference.get().toString());
    }

    static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
    static AtomicStampedReference<Integer> tomicStampedReference = new AtomicStampedReference<>(100, 1);

    public static void abaDemo() {
        //时间戳原子引用
        //每次值被修改后，加一个版本号以示区别
        /*
        T1  100 1           900 2
        T2  100 1   101 2   100 3
         */
        System.out.println("========以下是ABA问题的产生========");
        new Thread(() -> {
            atomicReference.compareAndSet(100, 101);
            atomicReference.compareAndSet(101, 100);
        }, "T1").start();

        new Thread(() -> {
            try {
                //暂停1秒钟，保证T1线程完成一次ABA操作
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(atomicReference.compareAndSet(100, 900) + "\t" + atomicReference.get());
        }, "T2").start();

        try {
            //暂停2秒钟，保证以上线程都已完成操作
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("========以下是ABA问题的解决========");

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t第1次版本号：" + tomicStampedReference.getStamp() + "\t值：" + tomicStampedReference.getReference());

            try {
                //T3线程暂停1秒钟
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            tomicStampedReference.compareAndSet(100, 101, 1, 2);
            System.out.println(Thread.currentThread().getName() + "\t第2次版本号：" + tomicStampedReference.getStamp() + "\t值：" + tomicStampedReference.getReference());

            tomicStampedReference.compareAndSet(101, 100, 2, 3);
            System.out.println(Thread.currentThread().getName() + "\t第3次版本号：" + tomicStampedReference.getStamp() + "\t值：" + tomicStampedReference.getReference());
        }, "T3").start();

        new Thread(() -> {
            int stamp = tomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "\t第1次版本号：" + stamp + "\t值：" + tomicStampedReference.getReference());

            try {
                //T4线程暂停3秒钟，保证T3完成一次ABA操作
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            tomicStampedReference.compareAndSet(100, 900, 1, 2);
            System.out.println(Thread.currentThread().getName() + "\t最终版本号：" + tomicStampedReference.getStamp() + " \t最终值：" + tomicStampedReference.getReference());
        }, "T4").start();
    }

}
